﻿
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using MDic = System.Collections.Generic.Dictionary<string, System.Func<object, object[], object>>;

namespace ClassProxitator
{
    public class ClassConnection : IConnection
    {
        public static readonly Dictionary<Type, MDic> DefaultMappings = new Dictionary<Type, MDic>();

        public static MDic GetMethodDictionary(MethodInfo[] methods)
        {
            var dic = new MDic(methods.Length);
            foreach (var m in methods)
            {
                dic.Add(m.Name, Method2Func(m));
            }
            return dic;
        }

        public static MDic GetMethodDictionary(Type type)
        {
            if (DefaultMappings.ContainsKey(type)) return DefaultMappings[type];
            var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            var r = GetMethodDictionary(
                methods.Where(m => (m.IsPublic)).ToArray()
            );
            DefaultMappings.Add(type, r);
            return r;
        }

        public static Func<object, object[], object> Method2Func(MethodInfo method)
        {
            var instance = Expression.Parameter(typeof(object));
            var parameters = Expression.Parameter(typeof(object[]));
            var callExp = Expression.Call(
                        Expression.Convert(instance, method.DeclaringType),
                        method,
                        method.GetParameters()
                            .Select((par, i) => Expression.Convert(Expression.ArrayIndex(parameters, Expression.Constant(i)), par.ParameterType)
                        )
                    );
            if (method.ReturnType == typeof(void))
            {
                var returnLabel = Expression.Label(typeof(object));
                var voidF = Expression.Lambda<Action<object, object[]>>(
                    callExp,
                    instance, parameters)
                    .Compile();
                return (a, b) => { voidF(a, b); return null; };
            }
            else {
                return Expression.Lambda<Func<object, object[], object>>(
                    Expression.Convert(callExp, typeof(object)),
                    instance, parameters)
                    .Compile();
            }
        }

        MDic methods = new Dictionary<string, Func<object, object[], object>>();
        object instance;

        public ClassConnection(object instance)
        {
            if (instance == null) throw new ArgumentNullException("instance");
            methods = GetMethodDictionary(instance.GetType());
            this.instance = instance;
        }

        public Task<object> CallMethod(string name, params object[] parameters)
        {
            return Task.Run(() => methods[name](this.instance, parameters));
        }
    }
}
